/*****************************************************************************\
*  Module Name    kernel_unique_ptr.hpp
*  Project        CppUtils
*
*  Description    Kernel safe unique_ptr.
*
\*****************************************************************************/

#pragma once

namespace CppUtils::ptr
{

/// 
/// k_unique_ptr:
///     Kernel safe unique_ptr.
///

//*** Class declaration ***

template<typename T>
class k_unique_ptr
{
public:
    // Empty constructor.
    k_unique_ptr();

    // Constructor. Takes ownership of ptr if not null.
    k_unique_ptr(T* ptr);

    // Move constructor. Passes ownership from other to this.
    k_unique_ptr(k_unique_ptr&& other);

    // Destructor.
    ~k_unique_ptr();

    // Move assignment. Passes ownership from other to this.
    k_unique_ptr& operator=(k_unique_ptr&& other);

    // Gets the underlying pointer, or null if empty.
    T* getPtr() const;

    // Dereference operator
    // provides access to the object pointed-to by the owned pointer.
    // Asserts if k_unique_ptr is empty.
    T* operator->() const;

    // Dereference operator
    // provides access to the object pointed-to by the owned pointer.
    // Asserts if k_unique_ptr is empty.
    T& operator*() const;

    // Returns true if the k_unique_ptr is not empty, false otherwise.
    explicit operator bool() const;

    // Frees the underlying pointer. The k_unique_ptr will become empty.
    void reset(T* ptr = nullptr);

    // Swaps ownership of two k_unique_ptrs.
    void swap(k_unique_ptr& other);

    // Release ownership of the underlying pointer and return it to the caller.
    // The k_unique_ptr will become empty.
    T* release();

private:
    // Non-copyable.
    k_unique_ptr(const k_unique_ptr&) = delete;
    k_unique_ptr& operator=(const k_unique_ptr&) = delete;

private:
    T* m_Ptr {nullptr};
}; // k_unique_ptr

//*** Class definition ***

template<class T>
k_unique_ptr<T>::k_unique_ptr() {}

template<class T>
k_unique_ptr<T>::k_unique_ptr(T* ptr) : m_Ptr(ptr) {}

template<class T>
k_unique_ptr<T>::k_unique_ptr(k_unique_ptr<T>&& other)
{
    // This contains nullptr on construction. No need for a proper swap.
    m_Ptr = other.m_Ptr;
    other.m_Ptr = nullptr;
}

template<class T>
k_unique_ptr<T>::~k_unique_ptr()
{
    if (nullptr != m_Ptr)
    {
        delete m_Ptr;
        m_Ptr = nullptr;
    }
}

template<class T>
k_unique_ptr<T>::operator bool() const
{
    return m_Ptr != nullptr;
}

template<class T>
T* k_unique_ptr<T>::getPtr() const
{
    return m_Ptr;
}

template<class T>
T* k_unique_ptr<T>::operator->() const
{
    if (nullptr == m_Ptr)
    {
        // ASSERT
    }
    return m_Ptr;
}

template<class T>
T& k_unique_ptr<T>::operator*() const
{
    if (nullptr == m_Ptr)
    {
        // ASSERT
    }
    return *m_Ptr;
}

template<class T>
k_unique_ptr<T>& k_unique_ptr<T>::operator=(k_unique_ptr<T>&& other)
{
    reset(other.release());
    return *this;
}

template<class T>
void k_unique_ptr<T>::reset(T* ptr)
{
    enum { err_on_incomplete_type = sizeof(T) };
    if (nullptr != m_Ptr)
    {
        delete m_Ptr;
    }
    m_Ptr = ptr;
}

template<class T>
void k_unique_ptr<T>::swap(k_unique_ptr<T>& other)
{
    T* tmp = m_Ptr;
    m_Ptr = other.m_Ptr;
    other.m_Ptr = tmp;
}

template<class T>
T* k_unique_ptr<T>::release()
{
    if (nullptr == m_Ptr)
    {
        // ASSERT
    }
    T* tmp = m_Ptr;
    m_Ptr = nullptr;
    return tmp;
}

}; // namespace CppUtils::ptr
